/*
 * @Author: soso
 * @Date: 2022-01-20 10:46:33
 * @LastEditTime: 2022-03-14 15:58:30
 * @LastEditors: Please set LastEditors
 * @Description: 服务端处理
 * @FilePath: /file-sync/core/cfiles/server.go
 */
package cfiles

import (
	"bytes"
	"context"
	"fmt"
	"gomeshsync/cfile"
	"gomeshsync/config"
	"gomeshsync/consts"
	"gomeshsync/pingpong"
	"log"
	"net"
	"os"
	"strconv"
	"time"

	"gitee.com/kimsoso/go-utils/sodirs"
	"gitee.com/kimsoso/go-utils/sonet"
	"github.com/vmihailenco/msgpack"
)

/**
 * @description: 监听端口，提供数据传输服务
 * @param {*}
 * @return {*}
 */
func SetListenService(listener net.Listener, ctx context.Context) {
	// 监听并接受来自客户端的连接
	for {
		conn, err := listener.Accept()
		if err != nil {
			// log.Println("listener closed:", err.Error())
			return
		}
		go doAcceptSocketConnect(conn, ctx)
	}
}

//网络命令解析
func getCommand(conn net.Conn) (cmd string, buf []byte, err error) {
	_, buf, err = sonet.Read(conn)

	if string(buf) == consts.COMM_CLOSE {
		cmd = consts.COMM_CLOSE

	} else if bytes.HasPrefix(buf, []byte(consts.COMM_PING)) {
		cmd = consts.COMM_PING

	} else if bytes.HasPrefix(buf, []byte(consts.CLI_DELFILES)) {
		cmd = consts.CLI_DELFILES

	} else if bytes.HasPrefix(buf, []byte(consts.CLI_DELFILE)) {
		cmd = consts.CLI_DELFILE

	} else if string(buf) == consts.COMM_BLOCKNEXT {
		cmd = consts.COMM_BLOCKNEXT

	} else if bytes.HasPrefix(buf, []byte(consts.COMM_REVBLOCK)) {
		cmd = consts.COMM_REVBLOCK

	} else if bytes.HasPrefix(buf, []byte(consts.COMM_ReqSendBlock)) {
		cmd = consts.COMM_ReqSendBlock

	} else if bytes.HasPrefix(buf, []byte(consts.COMM_REQNEWFILE)) {
		cmd = consts.COMM_REQNEWFILE

	} else if bytes.HasPrefix(buf, []byte(consts.COMM_SendNewFile)) {
		cmd = consts.COMM_SendNewFile

	} else if string(buf) == consts.CLI_FILE_LIST {
		cmd = consts.CLI_FILE_LIST

	} else if bytes.HasPrefix(buf, []byte(consts.CLI_SYNCFILE)) {
		cmd = consts.CLI_SYNCFILE

	} else if bytes.HasPrefix(buf, []byte(consts.CLI_RNMFILE)) {
		cmd = consts.CLI_RNMFILE

	} else if string(buf) == consts.COMM_REQDIRSTRUCT {
		cmd = consts.COMM_REQDIRSTRUCT

	}

	return
}

/**
 * @description: 协程函数，提供数据传输
 * @param {net.Conn} conn
 * @return {*}
 */
func doAcceptSocketConnect(conn net.Conn, ctx context.Context) {
	defer conn.Close()

	for {
		select {
		case <-ctx.Done():
			return
		default:
			cmd, bufCmd, err := getCommand(conn)
			if err != nil {
				return
			}

			switch cmd {
			case consts.COMM_PING: // ip活跃检测
				pingpong.NewPingpong(config.PathOfNodes).Pong(conn, bufCmd)
				return

			case consts.COMM_CLOSE: // 关闭该Node连接，释放routine
				return

			case consts.CLI_RNMFILE: // 改名文件
				if renameFile(bufCmd) {
					sonet.Write(conn, []byte(consts.COMM_OK))
				} else {
					sonet.Write(conn, []byte(consts.COMM_FAILED))
				}
				return

			case consts.COMM_BLOCKNEXT: // 本文件下个数据块
				continue

			case consts.COMM_REVBLOCK: // 处理数据块传输请求
				if !sendRevblockReply(conn, bufCmd) {
					return
				} else {
					continue
				}

			case consts.COMM_ReqSendBlock: //发送数据块给客户端
				sendBlockToClient(conn, bufCmd)
				return

			case consts.COMM_REQNEWFILE: //请求新命令

				//避免传送期间发送命令数据
				if canProcessCommands() {
					sendNewCommands(conn, bufCmd)
				}
				return

			case consts.COMM_SendNewFile: //下载远程文件
				if downloadRemoteFile(conn, bufCmd) {
					continue
				} else {
					return
				}
			case consts.CLI_DELFILE:
				deleteFileOrDir(bufCmd)
				return

			case consts.CLI_DELFILES:
				err = deleteFilesOrDirs(bufCmd)

				if err == nil {
					sonet.Write(conn, []byte(consts.COMM_OK))
				} else {
					sonet.Write(conn, []byte(consts.COMM_FAILED))
				}
				return

			case consts.CLI_SYNCFILE:
				params := bytes.Split(bufCmd, []byte(":"))
				targetDir := string(params[2])

				//reply OK
				err = sonet.Write(conn, []byte(consts.COMM_OK))
				if err != nil {
					return
				}

				// READ DATA
				syncFiles, err := readSyncfile(conn)
				if err != nil {
					log.Println("readSyncfile error:", err)
					return
				}

				//标注：新增、修改、相同 到 syncFiles
				compairWithLocalFiles(targetDir, &syncFiles)
				buf, _ := msgpack.Marshal(syncFiles)

				//回复 Data
				err = sonet.Write(conn, buf)
				if err != nil {
					log.Println("send back syncfile error:", err)
				}
				return
			case consts.COMM_REQDIRSTRUCT:
				dirsNode, err := sodirs.NewRootNode(config.DirFiles)
				if err != nil {
					return
				}

				dirsNode.LoadDirStruct()

				sonet.Write(conn, dirsNode.Serialize())
				return
			default:
				return
			}
		}
	}
}

func renameFile(command []byte) bool {
	params := bytes.Split(command, []byte(":"))
	fromPath := string(params[2])
	toPath := string(params[3])
	os.Rename(toRealSvrPath(fromPath), toRealSvrPath(toPath))

	SetRenameFileCommand(fromPath, toPath)
	return true
}

func sendNewCommands(conn net.Conn, combuf []byte) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	params := bytes.Split(combuf, []byte(":"))
	from, _ := strconv.Atoi(string(params[2]))

	cmds := MainCmds.GetCommandsFrom(from)

	sendBuf := []byte("")
	if len(cmds) > 0 && cmds[0].Ck == from {
		sendBuf, _ = msgpack.Marshal(cmds)
	}

	// log.Println("sendNewCommands from is:", from, "data len is:", len(cmds))
	// if len(cmds) > 0 {
	// 	log.Println("sendNewCommands last key is:", cmds[0].Ck, cmds[len(cmds)-1].Ck)
	// }

	sonet.Write(conn, sendBuf)
}

/**
 * @description: 从网络读取同步文件
 * @param {net.Conn} conn
 * @return {*}
 */
func readSyncfile(conn net.Conn) (syncFiles map[string]*Ufile, err error) {
	_, filesBytes, err := sonet.Read(conn)
	if err != nil {
		log.Println("readSyncfile read long data error:", err)
		return nil, err
	}

	syncFiles = map[string]*Ufile{}
	err = msgpack.Unmarshal(filesBytes, &syncFiles)
	return
}

/**
 * @description: 发送块数据
 * @param {net.Conn} conn
 * @param {[]Cfile} cachedFiles 本机文件列表
 * @param {[]byte} buf 从客户端读出的数据
 * @return {*}
 */
func sendRevblockReply(conn net.Conn, buf []byte) bool {
	params := bytes.Split(buf, []byte(":"))

	// 文件路径
	filePath := string(params[2])
	// 需要读取的文件块编码
	blockNumber, _ := strconv.Atoi(string(params[3]))

	// 检查数据块是否存在
	exists := checkBlock(filePath, blockNumber)
	switch exists {
	case 0:
		err := sonet.Write(conn, []byte(consts.COMM_BLOCKNOT)) //回复：不存该数据块
		if err != nil {
			log.Println("Error writing blocknot back:", err.Error())
		}

	case -1:
		err := sonet.Write(conn, []byte(consts.COMM_BLOCKEMPTY)) //回复：文件不存在或者没有数据块
		if err != nil {
			log.Println("Error writing ok back:", err.Error())
		}

	case 1:
		err := sonet.Write(conn, []byte(consts.COMM_OK)) //回复：存在数据块
		if err != nil {
			log.Println("Error writing ok back:", err.Error())
		}
		return true
	}
	return false
}

// 发送数据块
func sendBlockToClient(conn net.Conn, bufCommand []byte) {
	//读取客户端传来的数据块请求参数：filemd5, offset, size
	params := bytes.Split(bufCommand, []byte(":"))

	//文件唯一编码
	filePath := string(params[2])

	//文件偏移
	offset, _ := strconv.Atoi(string(params[3]))

	//数据块大小
	blockSize, _ := strconv.Atoi(string(params[4]))

	sonet.SendFileData(conn, toRealSvrPath(filePath), int64(offset), int64(blockSize), nil)
}

/**
 * @description: 接收上传文件
 * @param {net.Conn} conn
 * @param {[]byte} bufCommand
 * @return {*}
 */
func downloadRemoteFile(conn net.Conn, bufCommand []byte) (next bool) {
	next = false

	if TempCmds.Len() > 0 {
		sonet.Write(conn, []byte(consts.COMM_NOTREADY))
		return
	}

	isDir, filePath, remotefilesize := parseNewFileParams(bufCommand)

	if isDir {
		generateDirCmd(filePath)
		sonet.Write(conn, []byte(consts.COMM_OK))
		next = true
		return
	}

	if remotefilesize == 0 {
		generateAZeroLenFileCmd(filePath)
		sonet.Write(conn, []byte(consts.COMM_OK))
		next = true
		return
	}

	//实际的文件路径
	realFilePath := toRealSvrPath(filePath)
	//本地文件信息
	lfileinfo, errr := createUploadCmd(realFilePath)

	var localfilesize int64 = 0

	if errr != nil {
		err := touchAFile(realFilePath)
		if err != nil {
			log.Println("touch empty file err:", err)
			return
		}
	} else {
		localfilesize = lfileinfo.Size()
	}

	//准备接收
	err := sonet.Write(conn, []byte(fmt.Sprintf("%s:%d", consts.COMM_ReadyReceiveFile, localfilesize)))
	if err != nil {
		log.Println("write data error:", err)
		return
	}

	//接收数据
	if err := receiveFileData(conn, realFilePath, remotefilesize, localfilesize); err == nil {
		moveUploadCmdToMain(realFilePath)
		sonet.Write(conn, []byte(consts.COMM_OK))
		next = true
	} else {
		sonet.Write(conn, []byte(consts.COMM_CLOSE))
	}
	return
}

//生成增加目录的命令
func generateDirCmd(filePath string) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	cmd := &cfile.MCommand{
		Ck:         int(MainCmds.Nextkey()),
		Cmd:        cfile.IsADD,
		CreateTime: time.Now().Unix(),
		Done:       true,
		DoneTime:   time.Now().Unix(),
		Path:       filePath,
	}

	MainCmds.Add(cmd)
	os.Mkdir(toRealSvrPath(filePath), os.ModePerm)
}

//生成一个0长度文件和命令
func generateAZeroLenFileCmd(filePath string) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	cmd := &cfile.MCommand{
		Ck:         int(MainCmds.Nextkey()),
		CreateTime: time.Now().Unix(),
		Done:       true,
		DoneTime:   time.Now().Unix(),
		Path:       filePath,
	}

	realFilePath := toRealSvrPath(filePath)
	_, err := os.Stat(realFilePath)

	if err != nil {
		cmd.Cmd = cfile.IsADD
		touchAFile(realFilePath)
	} else {
		cmd.Cmd = cfile.IsUPDATE
		os.Truncate(realFilePath, 0)
	}

	cmd.File = &cfile.Cfile{
		BlockSize:       consts.BLOCKSIZE,
		Checked:         true,
		CreateTime:      time.Now().Format("2006-01-02 15:04:05"),
		CreateTimeStamp: time.Now().Unix(),
		FilePath:        filePath,
		FileSize:        0,
	}

	MainCmds.Add(cmd)
}

func CmdsLength() int {
	return int(MainCmds.Len())
}
